---
title: "3：プロキシ"
---

import FlagIcon from '@material-ui/icons/FlagSharp'
import SvgProxyPipelines from './proxy-pipelines.svg';

前回までは、Pipyはリクエストを処理するサーバーとして扱ってきました。ここでは、Pipyが最も使われる方法、ネットワークプロキシを解説します。

## プロキシプログラムを作る

左上のPipyアイコンをクリックして、Web UIホームページに戻ります。[パート1](/tutorial/01-hello#pipy-プログラムを作る)で行ったのと同じ方法で、新たに`/proxy`という名前のcodebaseを作成します。 

以前作った`/hello`プログラムは気にせず、残しておいてください。後のアップストリームサーバーのテストで使用します。

新しいcodebaseに`/proxy.js`という新しいファイルを追加し、ボタン <FlagIcon/> で入口にします。`/proxy.js`のスクリプトは次のようにします。

``` js
pipy()

.listen(8000)
  .demuxHTTP('forward')

.pipeline('forward')
  .muxHTTP('connection')

.pipeline('connection')
  .connect('localhost:8080')
```

## コードの説明

一つのポートパイプラインの他に、今回は「forward」 と 「connection」という二つのサブパイプラインを定義しました。 

1.  ポートパイプラインはポート8000をリッスンし、**demuxHTTP**というフィルターがひとつだけあります。これは**serveHTTP**と同じですが、HTTPリクエストをコールバック関数で処理するのではなく、サブパイプラインで処理します。サブパイプラインは、唯一の構成パラメータで 「forward」 という名前で参照されます。

2.	「forward」という名前のサブパイプラインには**muxHTTP**フィルターがあります。このフィルターはその入力として**demuxHTTP**からメッセージを受信し、入れ替わって、その構築パラメータで指定された「connection」という名前の別のサブパイプラインにメッセージを送ります。

3.	最後のパイプライン「connection」もまたサブパイプラインです。ここには唯一のフィルターである**connect**があり、このフィルターはすべての入力を構成パラメータ（"localhost:8080"）で指定されたリモートホストに送信し、そのホストから受信したすべてを出力します。

## Muxとdemux

以前行った「Hello World」のように１つのパイプラインではなく、なぜ３つもパイプラインを定義する必要があるのか疑問に思うかもしれません。これはHTTPのようなレベル７プロトコルが、TCPのようなレベル４プロトコルと比較してどのように動くかに起因しています。
簡単に言うと、レベル４では「ストリーム」として見えるものが、レベル７では個別の「メッセージ」が合体したストリームに見えるのです。

### フィルター：demuxHTTP

ポートパイプラインは着信するデータを[_Data_](/reference/api/Data)イベントとしてすべて受け入れ、それぞれがTCPストリ―ムの小さな塊を保持します。**demuxHTTP**がすることは、TCPストリームを個々のHTTPメッセージに「分割」すること、そしてその後の処理ために[_MessageStart_](/reference/api/MessageStart)イベントと[_MessageEnd_](/reference/api/MessageEnd)イベントとの間でそれらをラップすることです。

TCPストリームから抽出されたメッセージのそれぞれは、TCPレイヤーで動作する**demuxHTTP**のパイプラインとは別の、独立したパイプラインで処理されるようにすべきです。極端に単純化されたユースケースなら、送信側のストリームを流入させて、すべてのメッセージを同じパイプラインで処理することができるかもしれません。しかしほとんどのケースでは、メッセージ間で状態が混在し、誤ったロジックに陥ることになります。

**demuxHTTP**が、処理のために新しいサブパイプラインにメッセージを送信するよう設計されたのはこのためです。これは、入力ストリームから抽出されたすべてのメッセージに対して行われます。つまり、一つの着信接続を処理する一つのポートパイプラインが、それぞれのメッセージを個別に処理するために、**demuxHTTP**で多くのサブパイプラインに分岐されるということです。

### フィルター：muxHTTP

ここで試しているプロキシの例では、すべてのメッセージをアップストリームのサーバーに送る必要があります。HTTPメッセージがネットワーク経由で送られる前は、TCPストリーム内でラップされていなければなりません。しかし、メッセージごとにこれを個別に行うことはできません。それをすればストリ―ムが多くなり、送信接続が過多になってしまいます。

そこで、**muxHTTP**が必要になります。これはすべての入力メッセージを一つのTCPストリームに結合して同じパイプラインに投入します。そして最後のパイプラインが、この**connect**フィルターでアップストリームのサーバーと一つのTCP接続を確立し、データを前後に送ります。

### パイプラインのトポロジー

ここに一つの受信接続があり、そこで２つのリクエストを実行するとします。これを正しく処理するのには最大４つの相互に接続されたパイプラインが必要です。

<div style="text-align: center">
  <SvgProxyPipelines/>
</div>

## テストしてみる

`curl` でプロキシプログラムを開始します。

``` sh
curl localhost:8000 -i
```

するとエラーが返されます。

```
HTTP/1.1 502 Connection Refused
content-length: 0
connection: keep-alive
```

これはリクエストを投げるアップストリームのサーバーがまだ無いからです。

### 2番目のPipyインスタンスを起動する

現在のPipyインスタンスは一つだけで、既にこのプロキシプログラムが実行されています。そこで、テストサーバー用として2番目のインスタンスを起動する必要があります。

1.	Web UI ホームページに戻り、前に作成した `/hello` codebase を探して、クリックでこれを開きます。

2.	ブラウザのアドレスバーに表示されたURLをコピーします。

3.	別のターミナルウィンドウを開いて、 `pipy` とタイプし、その後ろにコピーしたURLを貼り付けると以下のようになります。

``` sh
pipy http://localhost:6060/repo/hello/
```

これで新しいPipyのインスタンスが起動して実行されます。先ほどのテストを再実行すると、上手くいけば以下のように答えが返されます。

```
HTTP/1.1 200 OK
content-length: 11
connection: keep-alive

Hi, there!
```

## まとめ

このパートでは、Pipyがネットワークプロキシとしてどのように動作するかを解説しました。そして、なぜサブパイプラインが必要なのか、なぜサブパイプラインが着信接続の数より多くなり得るのかを見てきました。

### 要点

1.	ポートパイプラインはTCPレイヤーでデータを受信します。個別のHTTPメッセージを扱うには、serveHTTPあるはdemuxHTTPのようなフィルターを使って、そこからメッセージを抽出する必要があります。

2.	serveHTTPとdemuxHTTPの違いは、前者がコールバック関数で抽出されたメッセージを処理するのに対し、後者は多くの個別のサブパイプラインでそれらを行うことです。

3.	HTTPメッセージがネットワーク経由で送られる前に、muxHTTPでHTTPメッセージをTCPストリームに結合しなければなりません。

4.	connectを使ってリモートホストとの間に発信TCP接続を確立します。

### 次のパートの内容

このパートは、ネットワークプロキシの練習の最初なので、過度に単純化しています。実際には、あるIP/portを別のIP/portにマッピングしているだけです。プロキシをもっと便利にするには、ルーティングやロードバランシングなどの機能性が必要です。次のパートでは、プロキシができる基本タスクのひとつ、ルーティングについて解説します。

